package scanner;

import parser.GrammarSymbols;
import util.Arquivo;
import util.symbolsTable.IdentificationTable;

/**
 * Scanner class
 * @version 2010-september-04
 * @discipline Compiladores
 * @author Gustavo H P Carvalho
 * @email gustavohpcarvalho@ecomp.poli.br
 */
public class Scanner {

	// The file object that will be used to read the source code
	private Arquivo file;
	// The last char read from the source code
	private char currentChar;
	// The kind of the current token
	private int currentKind;
	// Buffer to append characters read from file
	private StringBuffer currentSpelling;
	// Current line and column in the source file
	private int line, column;
	
	/**
	 * Default constructor
	 */
	public Scanner() {
		this.file = new Arquivo("C:\\Users\\AlexandreF\\Documents\\Facudade\\Compiladores\\fibonacci.txt");		
		this.line = 0;
		this.column = 0;
		this.currentChar = this.file.readChar();
	}
	
	/**
	 * Returns the next token
	 * @return
	 * @throws LexicalException 
	 */ 
	public Token getNextToken() throws LexicalException {
		// Initializes the string buffer
		this.currentSpelling = new StringBuffer("");

		// Ignores separators
		while (isSeparator(this.currentChar)) {
		this.scanSeparator();
		}

		// Clears the string buffer
		this.currentSpelling = new StringBuffer(""); // ou apagar o conte�do do buffer
		// Scans the next token
		this.currentKind = this.scanToken();

		// Creates and returns a token for the lexema identified
		return new Token(this.currentKind, this.currentSpelling.toString(), this.line, this.column);
	}
	
	/**
	 * Returns if a character is a separator
	 * @param c
	 * @return
	 */
	private boolean isSeparator(char c) {
		if ( c == '#' || c == ' ' || c == '\n' || c == '\t' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/*
	 * Reads (and ignores) a separator
	 * @throws LexicalException
	 */
	private void scanSeparator() throws LexicalException {
		if ( this.currentChar == '#' ) {
			this.getNextChar();
			while ( this.isGraphic(this.currentChar) ) {
				this.getNextChar();
			}
			if(this.currentChar == '\n'){
				this.getNextChar();
			}else{
				throw new LexicalException(null, currentChar, line, column);
			}

		} else {
			this.getNextChar();
		}
		// If it is a comment line
			// Gets next char
			// Reads characters while they are graphics or '\t'
			// A command line should finish with a \n
	}
	
	/**
	 * Gets the next char
	 */
	private void getNextChar() {
		// Appends the current char to the string buffer
		this.currentSpelling.append(this.currentChar);
		// Reads the next one
		this.currentChar = this.file.readChar();
		// Increments the line and column
		this.incrementLineColumn();
	}
	
	/**
	 * Increments line and column
	 */
	private void incrementLineColumn() {
		// If the char read is a '\n', increments the line variable and assigns 0 to the column
		if ( this.currentChar == '\n' ) {
			this.line++;
			this.column = 0;
		// If the char read is not a '\n' 
		} else {
			// If it is a '\t', increments the column by 4
			if ( this.currentChar == '\t' ) {
				this.column = this.column + 4;
			// If it is not a '\t', increments the column by 1
			} else {
				this.column++;
			}
		}
	}
	
	/**
	 * Returns if a char is a digit (between 0 and 9)
	 * @param c
	 * @return
	 */
	private boolean isDigit(char c) {
		if ( c >= '0' && c <= '9' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a letter (between a and z or between A and Z)
	 * @param c
	 * @return
	 */
	private boolean isLetter(char c) {
		if ( (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Returns if a char is a graphic (any ASCII visible character)
	 * @param c
	 * @return
	 */
	private boolean isGraphic(char c) {
		if ( c >= ' ' && c <= '~' ) {
			return true;
		} else {
			return false;
		}
	}
	
	/**
	 * Scans the next token
	 * Simulates the DFA that recognizes the language described by the lexical grammar
	 * @return
	 * @throws LexicalException
	 */ 
	private int scanToken() throws LexicalException {
		char cc = this.currentChar;
		int s = 0;
		IdentificationTable table = new IdentificationTable();
		while(true){
			cc = this.currentChar;
			switch (s) {
			case 0:
				if (isLetter(cc) || cc == '_'){
					s = 1;
					getNextChar();
					break;
				}else if(isDigit(cc)){
					s = 2;
					getNextChar();
					break;
				}else if(cc== '<'){
					s = 8;
					getNextChar();
					break;
				}else if(cc== '>'){
					s = 6;
					getNextChar();
					break;
				}else if(cc== ':'){
					s = 10;
					getNextChar();
					break;
				}else if(cc== '='){
					s = 13;
					getNextChar();
					break;
				}else if(cc== '+'){ 
					s = 11;
					getNextChar();
					break;
				}else if(cc== '('){
					s = 15;
					getNextChar();
					break;
				}else if(cc== ')'){
					s = 14;
					getNextChar();
					break;
				}else if(cc== '/'){
					s = 9;
					getNextChar();
					break;
				}else if(cc== ';'){
					s = 12;
					getNextChar();
					break;
				} else if(cc== ','){
					s = 20;
					getNextChar();
					break;
				}else if(cc == '$'){	
					s = 21;
					getNextChar();
					break;
				}else if(cc =='-'){
					s = 22;
					getNextChar();
					break;
				}else if(cc=='*'){
					s = 23;
					getNextChar();
					break;
				}else if(cc =='\000'){
					s = 24;
					break;
				}else{	
					s = 5;
					break;
				}
			case 1:
				if(isLetter(cc) || isDigit(cc) || cc == '_'){
					s = 1;
					getNextChar();
					break;
				}else{
					String temp = currentSpelling.toString();
					
					if(table.containsKey(temp)){
						return checkReservedWords(temp);
					}else{
						return GrammarSymbols.ID;
					}
					
				}
			case 2:
				if(isDigit(cc)){
					s = 2;
					getNextChar();
					break;
				}else if(cc== '.'){
					s = 3;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.NUMBER;
				}
			case 3:
				if(isDigit(cc)){
					s = 4;
					getNextChar();
					break;
				}else{
					s = 5;
					getNextChar();
					break;
				}
			case 4:
				if(isDigit(cc)){
					s = 4;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.NUMBER;
				}
			case 5:
				throw new LexicalException("ERRO LEXICO", cc, line, column);
			case 6:
				if(cc== '='){
					s = 7;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.GREATERTHAN;
				}
			case 7:
				return GrammarSymbols.GREATERTHANEQUAL;
			case 8:
				if(cc== '='){
					s = 16;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.LESSERTHAN;
				}
			case 9:
				if(cc== '='){
					s = 17;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.DIV;
				}
			case 10:
				if(cc== '='){
					s = 18;
					getNextChar();
					break;
				}else{
					return GrammarSymbols.COLON;
				}
			case 11:
					return GrammarSymbols.PLUS;
			case 12:
				return GrammarSymbols.SEMICOLON;
			case 13:
				if(cc== '='){
					s = 19;
					getNextChar();
					break;
				}else{
					s = 5;
					getNextChar();
					break;
				}
			case 14:
				return GrammarSymbols.RPAR;
			case 15:
				return GrammarSymbols.LPAR;
			case 16:
				return GrammarSymbols.LESSERTHANEQUAL;
			case 17:
				return GrammarSymbols.NOTEQUAL;
			case 18:
				return GrammarSymbols.ASG;
			case 19:
				return GrammarSymbols.EQUAL;
			case 20:
				return GrammarSymbols.COMMA;
			case 21:
				return GrammarSymbols.CURRENCY;
			case 22:
				return GrammarSymbols.MINUS;
			case 23:
				return GrammarSymbols.MULT;
			case 24:
				return GrammarSymbols.EOT;
			default:
				break;
			}
		}
		
		// While loop to simulate the automata
	}	
		public int checkReservedWords (String rs){
			if(rs.equals("if"))
				return GrammarSymbols.IF;
			else if(rs.equals("else"))
				return GrammarSymbols.ELSE;
			else if(rs.equals("then"))
				return GrammarSymbols.THEN;
			else if(rs.equals("while"))
				return GrammarSymbols.WHILE;
			else if(rs.equals("int"))
				return GrammarSymbols.INT;
			else if(rs.equals("float"))
				return GrammarSymbols.FLOAT;
			else if(rs.equals("return"))
				return GrammarSymbols.RETURN;
			else if(rs.equals("put_line"))
				return GrammarSymbols.PUT_LINE;
			else if(rs.equals("exit"))
				return GrammarSymbols.EXIT;
			else if(rs.equals("function"))
				return GrammarSymbols.FUNCTION;
			else if(rs.equals("procedure"))
				return GrammarSymbols.PROCEDURE;
			else if(rs.equals("is"))
				return GrammarSymbols.IS;
			else if(rs.equals("end"))
				return GrammarSymbols.END;
			else if(rs.equals("loop"))
				return GrammarSymbols.LOOP;
			else if(rs.equals("begin"))
				return GrammarSymbols.BEGIN;
			else if(rs.equals("true"))
				return GrammarSymbols.TRUE;
			else if(rs.equals("false"))
				return GrammarSymbols.FALSE;
			else
				return -1;
		}
	
		
	public Arquivo getFile() {
			return file;
		}

		public void setFile(Arquivo file) {
			this.file = file;
		}

		public char getCurrentChar() {
			return currentChar;
		}

		public void setCurrentChar(char currentChar) {
			this.currentChar = currentChar;
		}

		public int getCurrentKind() {
			return currentKind;
		}

		public void setCurrentKind(int currentKind) {
			this.currentKind = currentKind;
		}

		public StringBuffer getCurrentSpelling() {
			return currentSpelling;
		}

		public void setCurrentSpelling(StringBuffer currentSpelling) {
			this.currentSpelling = currentSpelling;
		}

		public int getLine() {
			return line;
		}

		public void setLine(int line) {
			this.line = line;
		}

		public int getColumn() {
			return column;
		}

		public void setColumn(int column) {
			this.column = column;
		}

}
